# from socket import socket, SOCK_STREAM, AF_INET
from datetime import datetime
import time
import tkinter as tk
import pandas as pd
from queue import Queue
import threading
from threading import Thread
from bidict import bidict
import json
import os

# import time
import traceback
from utils import process_logger, COMCODE, DateEncoder
from serversocket import serversocket
from serverframe import serverframe


class Server:
    def __init__(self, dialog_dir, user_dir, friend_dir):
        self.info_queue, self.op_queue = (
            Queue(),
            Queue(),
        )
        self.clients = bidict({})
        self.online_usr_id = {}
        self.lock_clients = threading.Lock()
        root = tk.Tk()
        root.title("Python chatbox server")
        root.protocol("WM_DELETE_WINDOW", self.close_server)
        self.serverframe = serverframe(
            root, self.op_queue, self.info_queue, self.online_usr_id
        )

        self.serversocket = serversocket(
            self.info_queue,
            self.op_queue,
            self.clients,
            self.lock_clients,
        )
        self.root = root
        self.dialog_dir = dialog_dir
        self.user_dir = user_dir
        self.friend_dir = friend_dir

        self.dialogs = pd.read_excel(self.dialog_dir)
        self.user_info = pd.read_excel(self.user_dir)
        self.friend_info = pd.read_excel(self.friend_dir)
        # self.friend_info = self.friend_info.astype('str')
        self.dialogs.time = self.dialogs.time.astype("datetime64")
        self.dialogs.msg = self.dialogs.msg.astype("str")

    def create(self):
        Thread(target=self.check_queue, daemon=True).start()
        self.serverframe.mainloop()

    def check_queue(self):
        while True:
            while not self.op_queue.empty():
                op = self.op_queue.get()
                print(op)
                eval("self." + op[0])(**op[1])
            time.sleep(0)

    def lis_start(self, port, ip):
        self.serversocket.lis_start(port, ip)

    def lis_stop(self):
        self.serversocket.lis_stop()

    def validate_usr(self, usr, psd, client):
        usr_idx = self.user_info["user_name"] == usr
        if not usr_idx.any():
            self.sendMsg(
                COMCODE["fail"], 1, "No such user.", client=client
            )
            return
        usr_item = self.user_info[usr_idx].values[0]
        if usr_item[0] in self.clients:
            self.sendMsg(
                COMCODE["fail"],
                1,
                "The user is online.",
                client=client,
            )
        elif usr_item[2] == psd:
            self.clients[usr_item[0]] = client
            self.serverframe.usr_ls.insert("end", usr_item[1])
            self.online_usr_id[usr_item[0]] = usr_item[1]
            self.serverframe.usr_ls.select_set(0, 0)
            self.loadMsg(usr_id=usr_item[0])
            self.sendMsg(
                COMCODE["success"],
                usr_item[0],
                "login",
                client=client,
            )
            print("validate clients", self.clients)
            self.sendUserList()
        else:
            self.sendMsg(
                COMCODE["fail"], 1, "Wrong password", client=client
            )

    @process_logger
    def register_usr(self, usr, psd, client):
        usr_idx = self.user_info["user_name"] == usr
        if usr_idx.any():
            self.sendMsg(
                COMCODE["fail"],
                1,
                "The user name is already used.",
                client,
            )
            return
        new_usr = {
            "id": self.user_info["id"].max() + 1,
            "user_name": usr,
            "passwd": psd,
        }
        self.user_info = pd.concat(
            [self.user_info, pd.DataFrame([new_usr])],
            ignore_index=True,
        )
        self.user_info.to_excel(self.user_dir, index=None)
        self.sendMsg(COMCODE["success"], 1, "register", client)

    @process_logger
    def sendUserList(self):
        for usr_id in self.online_usr_id:
            friend_lis = self.getFriends(usr_id)
            usr_item = dict(
                zip(
                    self.user_info["id"].to_list(),
                    self.user_info["user_name"].to_list(),
                )
            )
            friend_info = {i: usr_item[i] for i in friend_lis}
            for item in friend_info:
                with self.lock_clients:
                    if item in self.clients:
                        friend_info[item] += "(Online)"
                    else:
                        friend_info[item] += "(Offline)"
            other_info = {
                i: usr_item[i]
                for i in usr_item
                if i not in friend_lis
            }

            self.sendMsg(
                COMCODE["user_list"],
                usr_id,
                {
                    "friend_info": friend_info,
                    "other_info": other_info,
                },
                self.clients[usr_id],
            )

    @process_logger
    def loadLocal(self):
        with open("local_server", "r") as f:
            for line in f.readlines():
                if line == "":
                    return
                msg = json.loads(line[:-1])
                self.sendMsg(
                    msg["type"],
                    msg["user_id"],
                    msg["msg"],
                    # client=self.clients[0],
                    m_time=datetime.strptime(
                        msg["time"], "%Y-%m-%d %H:%M:%S"
                    ),
                    is_store=False,
                )
        with open("local_server", "w") as f:
            f.write("")

    def getFriends(self, usr_id):
        friend_lis = self.friend_info[
            (self.friend_info["user_1"] == usr_id)
            | (self.friend_info["user_2"] == usr_id)
        ]
        friend_lis = list(
            set(
                friend_lis["user_1"].to_list()
                + friend_lis["user_2"].to_list()
            )
        )
        if usr_id in friend_lis:
            friend_lis.remove(usr_id)
        return friend_lis

    @process_logger
    def loadMsg(self, usr_id):

        msg_item = (
            self.dialogs.apply(
                lambda row: {
                    self.dialogs.columns[i]: row[i]
                    for i in range(len(row))
                }
                if row["user_id"] == usr_id
                else None,
                axis=1,
            )
            .dropna()
            .to_list()[-5:]
        )
        for item in msg_item:
            self.sendMsg(
                item["type"],
                item["user_id"],
                item["msg"],
                self.clients[usr_id],
                item["time"],
                is_store=False,
            )

    @process_logger
    def storeMsg(self, msg_item):
        if msg_item["type"] not in [
            COMCODE["server_text"],
            COMCODE["server_file"],
            COMCODE["customer_text"],
            COMCODE["customer_file"],
        ]:
            return
        if msg_item["type"] in [2, 3]:
            msg_item["type"] -= 2
        new_dialog = pd.DataFrame([msg_item])
        self.dialogs = pd.concat(
            [self.dialogs, new_dialog], ignore_index=True
        )
        self.dialogs.to_excel(self.dialog_dir, index=None)
        self.serverframe.showMsg(msg_item)

    @process_logger
    def sendMsg(
        self,
        type,
        user_id,
        msg,
        client=None,
        m_time=datetime.now(),
        is_store=True,
    ):
        msg_item = {"type": type, "user_id": user_id, "time": m_time}
        if client is None:
            client = self.clients[user_id]
        if isinstance(msg, dict):
            msg_item.update(msg)
        else:
            msg_item["msg"] = msg

        if len(self.clients) == 0:
            self.serverframe.showMsg(msg_item)
            with open("local_server", "a") as f:
                f.write(json.dumps(msg_item, cls=DateEncoder) + "\n")
        else:
            self.serversocket.sendMsg(msg_item, client)
            if is_store:
                self.storeMsg(msg_item)
            else:
                self.serverframe.showMsg(msg_item)

    @process_logger
    def sendFile(self, select_files):
        client = self.clients[0]
        for selectFile in select_files:
            file_size = os.path.getsize(selectFile)
            self.sendMsg(
                2,
                1,
                [file_size, os.path.basename(selectFile)],
                client,
            )
            with open(selectFile, "rb") as f:
                client.sendfile(f)

    @process_logger
    def addFriend(self, usr_1, usr_2):
        new_friend = pd.DataFrame(
            [
                {
                    "user_1": usr_1,
                    "user_2": usr_2,
                }
            ]
        )
        self.friend_info = pd.concat(
            [self.friend_info, new_friend], ignore_index=True
        )
        self.friend_info.to_excel(
            self.friend_dir,
            index=None,
        )
        self.sendUserList()

    @process_logger
    def delFriend(self, usr_1, usr_2):
        self.friend_info.drop(
            self.friend_info.index[
                (
                    (self.friend_info["user_1"] == usr_1)
                    & (self.friend_info["user_2"] == usr_2)
                )
                | (
                    (self.friend_info["user_1"] == usr_2)
                    & (self.friend_info["user_2"] == usr_1)
                )
            ],
            inplace=True,
        )
        self.friend_info.to_excel(
            self.friend_dir,
            index=None,
        )
        self.sendUserList()

    def close_server(self):
        self.lis_stop()
        self.root.destroy()


dialog_dir = (
    "dialog.xlsx"
    if os.path.exists("dialog.xlsx")
    else "internet/exe_4/dialog.xlsx"
)
user_dir = (
    "user.xlsx"
    if os.path.exists("user.xlsx")
    else "internet/exe_4/user.xlsx"
)
friend_dir = (
    "friend.xlsx"
    if os.path.exists("friend.xlsx")
    else "internet/exe_4/friend.xlsx"
)
server = Server(dialog_dir, user_dir, friend_dir)


server.create()
